#include "VtxAccumulator.h"
#include "ViewManager.h"
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <cstdio>
#include <cstring>
#include "../../utils/fv_assert.h"
#include "fv_txt_utls.h"
#include "Log.h"
//#include <GL/glew.h>
//#define GL_GLEXT_PROTOTYPES
//#include <GL/glext.h>

namespace FemViewer {

VtxAccumulator::VtxAccumulator()
  :
    aColors                     (),
    aColorsFrozen               (false),
    aNormals                    (),
    aSimplifiedDirty            (true),
    aVertices                   (),
    aVerticesFrozen             (false),
	aNodes						(),
	aNodeColors					(),
	aNodeNormals				(),
	aIndices					(),
	aVBOUsed					(false),
	aRenderEdges				(false),	
	aRenderTraingleStrips		(false),
	aRenderTraingles			(false),
	aEdgeColor					(0.f, 0.f, 0.f),
	aEdgeThickness				(1.5f),
	aEdgeCount					(0)
{
	memset(aVBOIds,0x0, sizeof(aVBOIds));
}

VtxAccumulator::~VtxAccumulator()
{
	logd("Vtxacumulator: destructor\n");
	aNodes.clear();
	aNodeColors.clear();
	aNodeNormals.clear();
	aIndices.clear();
	cleanUp();
}

void VtxAccumulator::createVBO()
{
	// If VBO is not used than return
	if (!aVBOUsed) return;
	mfp_debug("Creating VBO\n");
	// Create buffers and load data into buffers than unbind
	// Create VBO for vertices
	assert(aNodes.size() > 0);
	assert(aNodeNormals.size() > 0);
	assert(aNodeColors.size() > 0);
	assert(aIndices.size() >0);
	unsigned int size = sizeof(NodeCoords)*aNodes.size();
	aVBOIds[0] = ViewManagerInst().CreateVBO(aNodes.data(),
			size,GL_ARRAY_BUFFER_ARB,GL_STATIC_DRAW);
	mfp_debug("After vertex VBO\n");
	FV_CHECK_ERROR_GL();

	// Create VBO for normals
	size = sizeof(NodeCoords)*aNodeNormals.size();
	aVBOIds[1] = ViewManagerInst().CreateVBO(aNodeNormals.data(),
			size,GL_ARRAY_BUFFER_ARB,GL_STATIC_DRAW);
	mfp_debug("After normal VBO\n");
	FV_CHECK_ERROR_GL();

	// Create VBO for colors
	size = sizeof(NodeColor)*aNodeColors.size();
	aVBOIds[2] = ViewManagerInst().CreateVBO(aNodeColors.data(),
			size,GL_ARRAY_BUFFER_ARB,GL_STATIC_DRAW);
	mfp_debug("After color VBO\n");
	FV_CHECK_ERROR_GL();

	// Create IBO for indices
	size = sizeof(unsigned int)*aIndices.size();
	aVBOIds[3] = ViewManagerInst().CreateVBO(aIndices.data(),
			size,GL_ELEMENT_ARRAY_BUFFER_ARB,GL_STATIC_DRAW);
	mfp_debug("After index VBO\n");
	FV_CHECK_ERROR_GL();

	if (aVBOIds[0] == 0 || aVBOIds[1] == 0 || aVBOIds[2] == 0 || aVBOIds[3] == 0) {
		mfp_debug("ERROR in VBO\n");
		throw "Can't allocate VBO!\n";
	}

}

void VtxAccumulator::cleanUp()
{
	//logd("VtxAccumulator: cleanUp\n");
	mfp_debug("Cleaning VBOs\n");
	/*if (aVBOUsed) {
		glDeleteBuffersARB(2, aVBOIds);
		//FV_CHECK_ERROR_GL();
	}*/
	if (aVBOIds[0]) {
		glDeleteBuffers(4, &aVBOIds[0]);
	}
	memset(aVBOIds,0x0,sizeof(aVBOIds));
	aVBOUsed = false;
	aBBox.Reset();
}

void VtxAccumulator::addColor(const Vec3D& pColor)
{
  aColors.push_back(pColor);

  aSimplifiedDirty = true;
}

//void VtxAccumulator::addTex(const Tex2D& pTex)
//{
//  aTexCoords.push_back(pTex);
//}

void VtxAccumulator::addVertex(const Vec3D& pVertex)
{
  aVertices.push_back(pVertex);

  aSimplifiedDirty = true;
}

#ifdef FV_DUMP_MEMORY_USAGE

template <class Container>
inline
std::string getStringSizeAndCapacity(const Container& pContainer)
{
  char lSizeAndCapacity[128];
  sprintf(lSizeAndCapacity,"%d/%d",
          sizeof(typename Container::value_type)*pContainer.size(),
          sizeof(typename Container::value_type)*pContainer.capacity());
  return std::string(lSizeAndCapacity);
}

#endif // #ifdef  FV_DUMP_MEMORY_USAGE

// Dump in ASCII the caracteristics of the VtxAccumulator
void VtxAccumulator::dumpCharacteristics(std::ostream&       pOstream,
                                            const std::string&  pIndentation,
                                            const Matrix<float>&    pTransformation) const
{
  pOstream << pIndentation << "VtxAccumulator " << std::endl;
  std::string lIndentation = pIndentation + "  ";

  if (aVerticesFrozen) {
    pOstream << lIndentation << "Vertices frozen = true" << std::endl;
  }
  else {
    pOstream << lIndentation << "Vertices frozen = false" << std::endl;
  }

  if (aColorsFrozen) {
    pOstream << lIndentation << "Colors frozen   = true" << std::endl;
  }
  else {
    pOstream << lIndentation << "Colors frozen   = false" << std::endl;
  }

 /* if (aTexCoordsFrozen) {
    pOstream << lIndentation << "TexCoords frozen   = true" << std::endl;
  }
  else {
    pOstream << lIndentation << "TexCoords frozen   = false" << std::endl;
  }*/

#ifdef FV_DUMP_MEMORY_USAGE
  {
    pOstream << lIndentation << "Memory used by the VtxAccumulator = " << sizeof(*this) << std::endl;

    pOstream << lIndentation << "Memory used by aColors    = " << getStringSizeAndCapacity(aColors  ) << std::endl;
    pOstream << lIndentation << "Memory used by aNormals   = " << getStringSizeAndCapacity(aNormals ) << std::endl;
    pOstream << lIndentation << "Memory used by aVertices  = " << getStringSizeAndCapacity(aVertices) << std::endl;
    pOstream << lIndentation << "Memory used by aTexCoords = " << getStringSizeAndCapacity(aTexCoords) << std::endl;

  }
#endif // #ifdef GLV_DUMP_MEMORY_USAGE

  pOstream << lIndentation << "Number of vertex  = " << aVertices  .size() << std::endl;
  pOstream << lIndentation << "Number of color_v = " << aColors    .size() << std::endl;
}

// Tell the VtxAccumulator that no more colors
// will be added to *this.
// Returns false if the number of colors is different
// from the number of vertices (this i bad and should
// be used to generate an error message
bool VtxAccumulator::freezeColors()
{
  FV_ASSERT(aVerticesFrozen);
  aColorsFrozen = true;

  const bool lColorsOk = (aColors.size() == aVertices.size());

  if (!lColorsOk) {
    // Remove the colors since they're not usable
    std::vector<Vec3D> lTmp;
    aColors.swap(lTmp);
  }

  return lColorsOk;
}

// Tell the VtxAccumulator that no more colors
// will be added to *this.
// Returns false if the number of colors is different
// from the number of vertices (this i bad and should
// be used to generate an error message
//bool VtxAccumulator::freezeTexCoords()
//{
//  FV_ASSERT(aVerticesFrozen);
//  aTexCoordsFrozen = true;
//
//  const bool lCoordsOk = (aTexCoords.size() == aVertices.size());
//
//  if (!lCoordsOk) {
//    // Remove the coords since they're not usable
//    std::vector<Tex2D> lTmp;
//    aTexCoords.swap(lTmp);
//  }
//
//  return lCoordsOk;
//}

// Tell the VtxAccumulator that no more vertices
// will be added to *this.
void VtxAccumulator::freezeVertices()
{
  FV_ASSERT(!aColorsFrozen);
  aVerticesFrozen = true;
}

// Return a reference to the internal data
const VtxAccumulator::Colors& VtxAccumulator::getColors() const
{
  return aColors;
}

// Return a reference to the internal data
//const VtxAccumulator::TexCoords& VtxAccumulator::getTexCoords() const
//{
//  return aTexCoords;
//}

// Return a reference to the internal data
const VtxAccumulator::Vertices& VtxAccumulator::getVertices() const
{
  return aVertices;
}

// Return a reference to the internal data
//  non-const because of it's computed into the VertexedPrimitiveAccumulators
VtxAccumulator::Normals& VtxAccumulator::getNormals()
{
  return aNormals;
}

// Return the state of the frozen flag; used for GLV_ASSERTs
bool VtxAccumulator::getVerticesFrozen() const
{
  return aVerticesFrozen;
}

// Return the state of the frozen flag; used for GLV_ASSERTs
bool VtxAccumulator::getColorsFrozen() const
{
  return aColorsFrozen;
}

void VtxAccumulator::render(const RenderParams& pPrams)
{
	if (aVBOUsed && aNodes.size() > 0 && aRenderEdges)
	{
		glColor3fv(aEdgeColor.v);
		glLineWidth(aEdgeThickness);
		//FV_CHECK_ERROR_GL();
		const size_t size = aIndices.size();
		//const fvmath::Vec3f *pv = aNodes.data();
		//glColor3f( 1.0f, 0.0f ,1.0f);
		glEnableClientState(GL_VERTEX_ARRAY);
		//FV_CHECK_ERROR_GL();
		glVertexPointer(3,GL_FLOAT,0,aNodes.data());
		//FV_CHECK_ERROR_GL();
		//glDrawArrays(GL_POINTS,0,aNodes.size());
		glDrawElements(GL_LINES,size,GL_UNSIGNED_INT, aIndices.data());
		glDisableClientState(GL_VERTEX_ARRAY);
		//FV_CHECK_ERROR_GL();

		// Draw text
		/*char buffer[12];
		glPushAttrib(GL_ENABLE_BIT);
		for(int iv(0);iv<aNodes.size();++iv)
		{
			itoa((iv+1),buffer,10); // 10 - decimal
			glRasterPos3f(pv[iv].x,pv[iv].y,pv[iv].z);
			int ic = 0;
			while(buffer[ic] !='\0') {
				glutBitmapCharacter(GLUT_BITMAP_8_BY_13, buffer[ic++]);
			}
		}

		glPopAttrib();*/
	}

	if (aRenderTraingleStrips) renderTriangleStrips(pPrams);
	if (aRenderTraingles) renderTriangles(pPrams);
}

void VtxAccumulator::renderTriangleStrips(const RenderParams& pParams)
{
	//glEnableClientState(GL_NORMAL_ARRAY);
	//mfp_debug("render strips\n");
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, aVBOIds[0]);
	glVertexPointer(3, GL_FLOAT, 0, 0);
	glBindBuffer(GL_ARRAY_BUFFER, aVBOIds[1]);
	glNormalPointer(GL_FLOAT,0,0);
	glBindBuffer(GL_ARRAY_BUFFER, aVBOIds[2]);
	glColorPointer(3, GL_FLOAT, 0, 0);
	//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, aVBOIds[3]);
	glEnableClientState(GL_VERTEX_ARRAY);
	//glEnableClientState(GL_NORMAL_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);

	//glNormalPointer(GL_FLOAT,0,aNodeNormals.data());
	//glColorPointer(3,GL_FLOAT,0,aNodeColors.data());
	//glVertexPointer(3,GL_FLOAT,0,aNodes.data());
	const unsigned int * pIndices = aIndices.data();
	GLint count;

	for (size_t itr(0); itr < aIndices.size(); itr+=count )
	{
		count = aIndices[itr++];
		glDrawElements(GL_TRIANGLE_STRIP, count, GL_UNSIGNED_INT, &pIndices[itr]);
		//FV_CHECK_ERROR_GL();
	}

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
	//glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);

	FV_CHECK_ERROR_GL();
}

void VtxAccumulator::renderTriangles(const RenderParams& pPr)
{
	const unsigned int * pIndices = aIndices.data();
	const GLsizei count = aIndices.size();
	//mfp_debug("Drawing trianges %d od %d nodes and %d colors edges %d\n",
	//		count,aNodes.size(),aNodeColors.size(),
	//		aEdgeCount);


	// Bind to VBO
	//glEnableVertexAttribArray(0);
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, aVBOIds[0]);
	//FV_CHECK_ERROR_GL();
	glVertexPointer(3, GL_FLOAT, 0, 0);
	//FV_CHECK_ERROR_GL();
	//glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void *)0);

	//glEnableVertexAttribArray(1);
	glBindBuffer(GL_ARRAY_BUFFER, aVBOIds[1]);
	glNormalPointer(GL_FLOAT,0,0);
	//FV_CHECK_ERROR_GL();
	//glVertexAttribPointer(1, 3, GL_FLOAT, GL_TRUE, 0, (void *)0);

	//glEnableVertexAttribArray(2);
	glBindBuffer(GL_ARRAY_BUFFER, aVBOIds[2]);
	glColorPointer(3, GL_FLOAT, 0, 0);

	glEnableClientState(GL_VERTEX_ARRAY);
	//FV_CHECK_ERROR_GL();
	glEnableClientState(GL_NORMAL_ARRAY);
	//FV_CHECK_ERROR_GL();
	glEnableClientState(GL_COLOR_ARRAY);
	//FV_CHECK_ERROR_GL();
	//glVertexAttribPointer(1, 3, GL_FLOAT, GL_TRUE, 0, (void *)0);

	//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, aVBOIds[3]);




	//glNormalPointer(GL_FLOAT,0,aNodeNormals.data());
	//glColorPointer(3,GL_FLOAT,0,aNodeColors.data());
	//glVertexPointer(3,GL_FLOAT,0,aNodes.data());

	glDrawArrays(GL_TRIANGLES, 0, count);
	//FV_CHECK_ERROR_GL();
	//glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT,(void *)0);
	//glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT,&pIndices[0]);


	//glDisableVertexAttribArray(0);
	//glDisableVertexAttribArray(1);
	//glDisableVertexAttribArray(2);
	//FV_CHECK_ERROR_GL();

	glDisableClientState(GL_VERTEX_ARRAY);
	//FV_CHECK_ERROR_GL();
	glDisableClientState(GL_NORMAL_ARRAY);
	//FV_CHECK_ERROR_GL();
	glDisableClientState(GL_COLOR_ARRAY);

	glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
	//FV_CHECK_ERROR_GL();
}




} // end namespace FemViewer
